<template>
  <main id="simulation-process" @click="deactiveMenuBarAndRightClickMenu">
    <section class="head">
      <page-header
        :title="projectName"
        @changeTitle="hangleChangeTitle"
      ></page-header>
      <menu-bar
        :actived="menuBarActived"
        @changeActived="handleChangeMenuBarActived"
      ></menu-bar>
      <top-toolbar></top-toolbar>
    </section>
    <section class="body">
      <item-panel class="left"></item-panel>
      <div class="right">
        <breadcrumb
          class="breadcrumb-wrapper"
          :breads="computedBreadArr"
          @changeBreadArr="handleBreadArrChange"
        ></breadcrumb>
        <div class="editor-panel-wrapper" ref="editor-panel-wrapper">
          <div id="editor-panel"></div>
        </div>
        <props-panel></props-panel>
      </div>
    </section>

    <props-dialog
      :visible="propsDialogVisible"
      :node="doubleClickNodeData"
      @cancel="handleDialogCancel"
      @confirm="handleDialogConfirm"
      @mux_in_port_change="handleMuxInPortChange"
      @mux_out_port_change="handleMuxOutPortChange"
    ></props-dialog>
    <right-click-menu
      :actived="rightClickMenuActived"
      :node="rightClickNodeData"
      :position="rightClickPosition"
    ></right-click-menu>
    <inner-func-editor
      :visible="innerFuncEditorDialogVisible"
      :node="rightClickNodeData"
      @close="handleInnerFuncEditorClose"
      @updateFunc="handleFuncUpdate"
    ></inner-func-editor>
  </main>
</template>

<script>
// import { v4 as uuidV4 } from "uuid";
// import { Markup } from "@antv/x6";
import { Node } from "@antv/x6";
import axios from "axios";

import PageHeader from "./components/page-header/index.vue";
import MenuBar from "./components/menu-bar/index.vue";
import TopToolbar from "./components/top-toolbar/index.vue";
import ItemPanel from "./components/item-panel/index.vue";
import breadcrumb from "./components/breadcrumb/index.vue";
import PropsPanel from "./components/props-panel/index.vue";
import PropsDialog from "./components/props-dialog/index.vue";
import RightClickMenu from "./components/right-click-menu/index.vue";
import InnerFuncEditor from "./components/inner-func-editor/index.vue";

import GraphEditor from "./core/graph";
import generateNode from "./core/graph/nodes/generateNode";
import EventBus from "./event-bus";
import BusEvents from "./event-bus/event-types";

export default {
  name: "simulation-process",
  components: {
    PageHeader,
    MenuBar,
    TopToolbar,
    ItemPanel,
    breadcrumb,
    PropsPanel,
    PropsDialog,
    RightClickMenu,
    InnerFuncEditor,
  },
  data() {
    return {
      // 获取 ModelEditor 的 graph 和 dnd 对象，用以操作
      graph: null,
      dnd: null,
      // 其他页面的属性
      hoverItem: {},
      doubleClickNode: { data: { inArr: [], outArr: [] } },
      doubleClickNodeData: { type: "." },
      rightClickNode: { data: { inArr: [], outArr: [] } },
      rightClickNodeData: {},
      rightClickPosition: { x: undefined, y: undefined },

      id2nameMap: new Map(),
      idBreadArr: [],

      projectName: "",
      projectGraphData: [], // { cells: [] }，projectGraphData 只包含节点数据，是一个 数组

      menuBarActived: false,
      rightClickMenuActived: false,

      propsDialogVisible: false,
      innerFuncEditorDialogVisible: false,
    };
  },
  computed: {
    computedBreadArr() {
      if (this.idBreadArr.length === 0) {
        let breadArr = [];
        breadArr.push(this.projectName);
        return breadArr;
      } else {
        let breadArr = this.idBreadArr.map((nodeId) => {
          return this.id2nameMap.get(nodeId);
        });
        breadArr.unshift(this.projectName);
        return breadArr;
      }
    },
  },
  mounted() {
    this.init();
  },
  beforeDestroy() {
    window.removeEventListener("resize", this.handleResize());
  },
  methods: {
    // * 初始化
    /**
     * 初始化函数
     */
    init() {
      const editorPanelWrapper = this.$refs["editor-panel-wrapper"];
      // const minimapWrapper = this.$refs["minimap-wrapper"];
      const minimap = document.getElementById("minimap"); // 不推荐这么使用
      // const editor = new GraphEditor(editorPanelWrapper, minimapWrapper);
      const editor = new GraphEditor(editorPanelWrapper, minimap);
      this.graph = editor.graph;
      this.dnd = editor.dnd;

      // if (this.initGraphData) {
      //   this.graph.fromJSON(this.initGraphData);
      // }
      this.initGraph();

      this.bindEvents();
      this.handleBusEvents();
    },
    initGraph() {
      axios
        .get("/api/getProject", {
          params: {
            projectId: this.$route.params.id,
          },
        })
        .then((res) => {
          const curProject = res.data.data;
          this.projectName = curProject.name;
          const initGraphData = curProject.projectData;
          this.graph.fromJSON(initGraphData);
          // 更新 id2nameMap
          this.initId2NameMap(initGraphData);
        })
        .catch((err) => {
          console.log(err);
        });
    },
    initId2NameMap(initGraphData) {
      initGraphData.forEach((cellData) => {
        if (!cellData.source) {
          const cellId = cellData.id;
          const cellName = cellData.data.name;
          this.id2nameMap.set(cellId, cellName);
          if (cellData.data.isGroup) {
            this.initId2NameMap(cellData.data.kids);
          }
          if (cellData.data.hasFunc) {
            this.initId2NameMap(cellData.data.func);
          }
        }
      });
    },

    // * 监听所有的事件
    /**
     * 监听所有的总线事件
     */
    handleBusEvents() {
      this.handleEventsFromItemPanel();
      this.handleEventsFromMenuBar();
      this.handleEventsFromTopToolbar();
      this.handleEventsFromEditorPanel();
      this.handleEventsFromRightClickMenu();
    },
    /**
     * 监听从 item-panel 触发的总线事件
     */
    handleEventsFromItemPanel() {
      EventBus.$on(BusEvents.INTERACT_ITEM_DRAG, ({ nodeData, e }) => {
        const { graph } = this;
        const node = generateNode(graph, nodeData);
        this.dnd.start(node, e);
      });
      EventBus.$on(BusEvents.INTERACT_ADD_NODE, ({ id, name }) => {
        this.id2nameMap.set(id, name);
        // 在 nextTick 中，为 subsystem 更新 ports
        this.$nextTick(() => {
          this.addPortToSubsystem(id);
        });
      });
      EventBus.$on(BusEvents.INTERACT_MOUSE_ENTER, ({ item }) => {
        this.hoverItem = item;
      });
      EventBus.$on(BusEvents.INTERACT_MOUSE_LEAVE, () => {
        this.hoverItem = {};
      });
    },
    /**
     * 监听从 menu-bar 触发的总线事件
     */
    handleEventsFromMenuBar() {
      // undo
      EventBus.$on(BusEvents.COMMAND_UNDO, () => {
        const { graph } = this;
        graph.history.undo();
        graph.cleanSelection();
      });
      // redo
      EventBus.$on(BusEvents.COMMAND_REDO, () => {
        const { graph } = this;
        graph.history.redo();
        graph.cleanSelection();
      });
      // copy
      EventBus.$on(BusEvents.COMMAND_COPY, () => {
        const { graph } = this;
        const cells = graph.getSelectedCells();
        if (cells.length) {
          graph.copy(cells);
        }
      });
      // cut
      EventBus.$on(BusEvents.COMMAND_CUT, () => {
        const { graph } = this;
        const cells = graph.getSelectedCells();
        if (cells.length) {
          graph.cut(cells);
          cells.forEach((cell) => {
            if (cell.isNode()) {
              this.deletePortFromSubsystem(cell);
            }
          });
        }
      });
      // paste
      EventBus.$on(BusEvents.COMMAND_PASTE, () => {
        const { graph } = this;
        if (!graph.isClipboardEmpty()) {
          const cells = graph.paste({ offset: 32 });
          graph.cleanSelection();
          graph.select(cells);

          cells.forEach((cell) => {
            if (cell.isNode()) {
              cell.getData().id = cell.id;
            }
          });

          cells.forEach((cell) => {
            if (cell.isNode()) {
              this.addPortToSubsystem(cell);
            }
          });
        }
      });
      // delete
      EventBus.$on(BusEvents.COMMAND_DELETE, () => {
        const { graph } = this;
        const cells = graph.getSelectedCells();
        if (cells.length) {
          graph.removeCells(cells);
          cells.forEach((cell) => {
            if (cell.isNode()) {
              this.deletePortFromSubsystem(cell);
            }
          });
        }
      });
      // save
      EventBus.$on(BusEvents.COMMAND_SAVE, () => {
        this.saveCurrentGraph();
        console.log(this.projectGraphData);
        // todo: 这里需要发送请求，进行数据的保存
        // const graphData = graph.toJSON();
        axios
          .post("/api/updateProject", {
            projectId: this.$route.params.id,
            updateTime: this.dateFormatFn(new Date()),
            projectData: this.projectGraphData,
          })
          .then((res) => {
            this.$message({
              message: res.data.message,
              type: "success",
            });
          })
          .catch((err) => {
            console.log(err);
          });
      });
      // todo: COMMAND_EXEC
      /**
       * () => {
       *  axios
          .post("/api/updateProject", {
            projectId: this.$route.params.id,
            updateTime: this.dateFormatFn(new Date()),
            projectData: this.projectGraphData,
          })
          .then((res) => {
            this.$message({
              message: res.data.message,
              type: "success",
            });
            this.graph.fromJSON({cells: res.data.projectData})
          })
          .catch((err) => {
            console.log(err);
          });
       * }
       */
    },
    /**
     * 监听从 top-toolbar 触发的总线事件
     */
    handleEventsFromTopToolbar() {
      // * 将 TopToolbar 中的事件都移到了 MenuBar 中进行统一管理
      // * 因为理论上 TopToolbar 中的命令是 MenuBar 的一部分
    },
    /**
     * 监听从 editor-panel 触发的总线事件
     */
    handleEventsFromEditorPanel() {
      // 监听节点双击事件
      EventBus.$on(BusEvents.INTERACT_DOUBLE_CLICK_NODE, ({ node }) => {
        // 在进入之前，先清空原先的选择
        this.graph.cleanSelection();
        // 默认是 Node 节点
        if (node instanceof Node) {
          this.doubleClickNode = node;
          const doubleClickNodeData = node.getData();
          this.doubleClickNodeData = doubleClickNodeData;
        } else {
          // 否则就是 NodeData
          this.doubleClickNodeData = node;
          const id = node.id;
          this.doubleClickNode = this.graph.getCellById(id);
        }

        // * 1.子系统节点
        if (this.doubleClickNodeData.isGroup) {
          // * 1.1 将当前 breads 对应的图更新到对应的 breads
          this.saveCurrentGraph();

          // * 1.2 更新 breads
          this.idBreadArr.push(node.id);

          // * 1.2 获取双击节点对应的 breads 的图并更新到 graph 上
          this.renderCurrentGraph();
        } else {
          // * 2. 其他节点
          this.propsDialogVisible = true;
        }
      });
      // 监听节点右击事件
      EventBus.$on(BusEvents.INTERACT_RIGHT_CLICK_NODE, ({ x, y, node }) => {
        this.rightClickNode = node;
        this.rightClickNodeData = node.getData();
        this.handleShowRightClickMenu(x, y);
      });
      // 监听修改节点名字的事件
      EventBus.$on(
        BusEvents.INTERACT_UPDATE_ID2NAME_MAP,
        ({ id, name, type }) => {
          // 更新 id2nameMap
          this.id2nameMap.set(id, name);
          // 更新节点的名字，如果是 port 节点，则需要更新对应的 subsystem 的 port 的名字
          if (type === "port") {
            // 获取包含 subsystem 的那一级的所有数据
            let current = this.idBreadArr.slice(0, -1).reduce((arr, key) => {
              for (let i = 0; i < arr.length; i++) {
                if (arr[i].id === key) {
                  return arr[i].data.kids;
                }
              }
            }, this.projectGraphData);
            // 根据 id 在 current 中查找到对应的 subsystem 节点数据
            let subsystemId = this.idBreadArr[this.idBreadArr.length - 1];
            let subsystemNode;
            for (let i = 0, len = current.length; i < len; i++) {
              if (subsystemId === current[i].id) {
                subsystemNode = current[i];
              }
            }
            // 拿到 subsystem 对应的节点数据后，为其增加 port
            if (subsystemNode) {
              let portItems = subsystemNode.ports.items;
              let updateItem;
              for (let i = 0; i < portItems.length; i++) {
                if (portItems[i].id === id) {
                  updateItem = portItems[i];
                }
              }
              updateItem.name = name;
            }
          }
        }
      );
      // 监听节点移动的事件
      EventBus.$on(BusEvents.INTERACT_MOVE_UP, () => {
        let selectedCells = this.graph.getSelectedCells();
        selectedCells.forEach((cell) => {
          if (cell.isNode()) {
            cell.translate(0, -5);
          }
        });
      });
      EventBus.$on(BusEvents.INTERACT_MOVE_RIGHT, () => {
        let selectedCells = this.graph.getSelectedCells();
        selectedCells.forEach((cell) => {
          if (cell.isNode()) {
            cell.translate(5, 0);
          }
        });
      });
      EventBus.$on(BusEvents.INTERACT_MOVE_DOWN, () => {
        let selectedCells = this.graph.getSelectedCells();
        selectedCells.forEach((cell) => {
          if (cell.isNode()) {
            cell.translate(0, 5);
          }
        });
      });
      EventBus.$on(BusEvents.INTERACT_MOVE_LEFT, () => {
        let selectedCells = this.graph.getSelectedCells();
        selectedCells.forEach((cell) => {
          if (cell.isNode()) {
            cell.translate(-5, 0);
          }
        });
      });
    },
    /**
     * 监听从 right-click-menu 触发的总线事件
     */
    handleEventsFromRightClickMenu() {
      EventBus.$on(BusEvents.INTERACT_ENTER_FUNC_EDITOR, () => {
        // 在进入之前，先清空原先的选择
        this.graph.cleanSelection();
        this.innerFuncEditorDialogVisible = true;
      });
    },
    /**
     * 监听 window 事件
     */
    bindEvents() {
      window.addEventListener("resize", this.handleResize());
    },
    handleResize() {
      return debounce(() => {
        const { graph } = this;
        const container = graph.container;
        const width = container.clientWidth;
        const height = container.clientHeight;
        graph.resize(width, height);
        graph.centerContent();
      });

      function debounce(func, wait) {
        let timeout = null;
        return function () {
          const context = this;
          const args = arguments;
          clearTimeout(timeout);
          timeout = setTimeout(() => {
            func.apply(context, args);
          }, wait);
        };
      }
    },

    // * 其他组件的处理函数
    /**
     * breadcrumb 组件的控制
     */
    // breadcrumb 点击 --> 改变 的事件监听
    handleBreadArrChange(clickIndex) {
      // this.idBreadArr = this.idBreadArr.slice(0, clickIndex);
      if (this.idBreadArr.length !== clickIndex) {
        // * 在更新 this.breads 之前更新数据，将 oldBreads 对应的数据更新到 projectGraphData 上
        this.saveCurrentGraph();
        // * 更新 idbreadArr
        this.idBreadArr = this.idBreadArr.slice(0, clickIndex);
        // * 更新 graph
        this.renderCurrentGraph();
      }
    },
    /**
     * menu-bar 组件的控制
     */
    // menu-bar 和 right-click-menu 激活项改变的监听
    deactiveMenuBarAndRightClickMenu() {
      this.menuBarActived = false;
      this.rightClickMenuActived = false;
    },
    // menu-bar 激活项改变的监听
    handleChangeMenuBarActived(newVal) {
      if (this.menuBarActived !== newVal) {
        this.menuBarActived = newVal;
      }
    },
    /**
     * right-clcik-menu 组件的控制
     */
    // right-click-menu 激活项改变的监听
    handleShowRightClickMenu(x, y) {
      this.rightClickMenuActived = true;
      this.rightClickPosition = { x, y };
    },
    /**
     * page-header 组件的控制
     */
    // page-header 修改项目名称
    hangleChangeTitle(newTitle) {
      if (this.projectName !== newTitle) {
        // this.$emit("changeProjectName", newTitle);
        this.projectName = newTitle;
      }
    },
    /**
     * props-dialog 组件的控制
     */
    handleDialogCancel() {
      this.propsDialogVisible = false;
    },
    handleDialogConfirm() {
      this.propsDialogVisible = false;
    },
    handleMuxInPortChange({ id, val }) {
      // console.log(val[0].paramRes);
      // const node = this.graph.getCellById(id);
      // console.log(node.port.ports);
      console.log(id, val);
    },
    handleMuxOutPortChange({ id, val }) {
      console.log(id, val);
    },
    /**
     * inner-func-editor 组件的控制
     */
    handleInnerFuncEditorClose() {
      this.innerFuncEditorDialogVisible = false;
    },
    handleFuncUpdate(data) {
      this.rightClickNodeData.func = data.func;
    },

    // * 其他逻辑函数
    /**
     * 为 subsystem 添加 port
     * 使用场景：
     * 1. 从 itemPanel 中拖拽生成给节点的时候，可能会添加 port 节点（BusEvents.INTERACT_ADD_NODE）
     * 2. 使用 commandMenu 中粘贴命令粘贴节点的时候，可能会粘贴出 port 节点（BusEvents.COMMAND_PASTE）
     */
    addPortToSubsystem(idOrNode) {
      let node;
      // 如果 idOrNode 的类型是 string，则判定它是 id
      if (typeof idOrNode === "string") {
        node = this.graph.getCellById(idOrNode);
      } else {
        node = idOrNode;
      }

      // console.log(node);

      // 这里的 node 是对应的 in 或者 out 连接桩
      // let node = this.graph.getCellById(id);
      const { type } = node.getData();
      let [nodeClass, nodeType] = type.split(".");
      // * 如果 node 的节点类型是 port，而且 idBreadArr 指示不在项目层，可以为 subsystem 添加连接桩 port
      if (nodeClass === "port" && this.idBreadArr.length !== 0) {
        // 1. 获取包含 subsystem 节点的那一级的所有数据
        let current = this.idBreadArr.slice(0, -1).reduce((arr, key) => {
          for (let i = 0; i < arr.length; i++) {
            if (arr[i].id === key) {
              return arr[i].data.kids;
            }
          }
        }, this.projectGraphData);
        // 2. 根据 subsystem 的 id 查找到对应的 subsystem 节点
        let subsystemId = this.idBreadArr[this.idBreadArr.length - 1];
        let subsystemNode;
        for (let i = 0, len = current.length; i < len; i++) {
          if (subsystemId === current[i].id) {
            subsystemNode = current[i];
          }
        }
        // 3. 给 subsystem 节点添加 port
        if (subsystemNode) {
          let portItems = subsystemNode.ports.items;
          if (nodeType === "in") {
            portItems.push({
              id: node.id, // 用节点的 id 作为 port 的 id
              name: "in",
              group: "in",
            });
          } else if (nodeType === "out") {
            portItems.push({
              id: node.id, // 用节点的 id 作为 port 的 id
              name: "out",
              group: "out",
            });
          }
        }
      }
    },
    /**
     * 为 subsystem 删除 port
     * 使用场景
     * 1. 使用 commandMenu 中的删除命令删除节点的时候，可能会删除 port 节点（BusEvents.COMMAND_DELETE）
     * 2. 使用 commandMenu 中的剪切命令剪切节点的时候，可能会剪切 port 节点（BusEvents.COMMAND_CUT）
     */
    deletePortFromSubsystem(idOrNode) {
      let node;
      // 如果 idOrNode 的类型是 string，则判定它是 id
      if (typeof idOrNode === "string") {
        node = this.graph.getCellById(idOrNode);
      } else {
        node = idOrNode;
      }

      // console.log(node);

      // 这里的 node 是对应的 in 或者 out 连接桩
      // let node = this.graph.getCellById(id);
      const { type } = node.getData();
      const nodeId = node.id;
      let nodeClass = type.split(".")[0];
      // * 如果 node 的节点类型是 port，而且 idBreadArr 指示不在项目层，可以为 subsystem 添加连接桩 port
      if (nodeClass === "port" && this.idBreadArr.length !== 0) {
        // 1. 获取包含 subsystem 节点的那一级的所有数据
        let current = this.idBreadArr.slice(0, -1).reduce((arr, key) => {
          for (let i = 0; i < arr.length; i++) {
            if (arr[i].id === key) {
              return arr[i].data.kids;
            }
          }
        }, this.projectGraphData);
        // 2. 根据 subsystem 的 id 查找到对应的 subsystem 节点
        let subsystemId = this.idBreadArr[this.idBreadArr.length - 1];
        let subsystemNode;
        for (let i = 0, len = current.length; i < len; i++) {
          if (subsystemId === current[i].id) {
            subsystemNode = current[i];
          }
        }
        // 3. 给 subsystem 节点删除 port
        if (subsystemNode) {
          let portItems = subsystemNode.ports.items;
          let deleteIdx;
          portItems.forEach((portItem, index) => {
            if (portItem.id === nodeId) {
              deleteIdx = index;
            }
          });
          if (deleteIdx) {
            portItems.splice(deleteIdx, 1);
          }
        }
      }
    },

    /**
     * 根据当前的 bread 保存数据
     * 如果在每一个 子系统subsystem 下都保存了相应的数据，那么整个图的数据就是完好的
     */
    saveCurrentGraph() {
      // 1. 根据 this.idBreadArr 获取到当前的子系统的节点
      let current = this.idBreadArr.reduce((arr, key) => {
        for (let i = 0; i < arr.length; i++) {
          if (arr[i].id === key) {
            return arr[i].data.kids;
          }
        }
      }, this.projectGraphData);
      // 2. 更新当前 current 子系统的数据
      current.splice(0, current.length); // 删除所有原来的数据
      const currentGraphDataArr = this.graph.toJSON()["cells"]; // 获取当前图的所有数据
      for (let i = 0; i < currentGraphDataArr.length; i++) {
        current.push(currentGraphDataArr[i]);
      }
    },
    /**
     * 根据当前的 bread 渲染出对应的图
     */
    renderCurrentGraph() {
      // 1. 根据 this.idBreadArr 获取对应的子系统的图数据
      let currentGraphData = this.idBreadArr.reduce((arr, key) => {
        for (let i = 0; i < arr.length; i++) {
          if (arr[i].id === key) {
            return arr[i].data.kids;
          }
        }
      }, this.projectGraphData);
      // 2. 将对应的图数据更新到 this.graph 上
      this.graph.fromJSON({ cells: currentGraphData });
    },
    /**
     * 时间格式化函数
     */
    dateFormatFn(date, format = "YYYY-MM-DD HH:mm:ss") {
      let config = {
        YYYY: date.getFullYear(),
        MM:
          date.getMonth() + 1 > 10
            ? date.getMonth() + 1
            : "0" + (date.getMonth() + 1),
        DD: date.getDate() > 10 ? date.getDate() : "0" + date.getDate(),
        HH: date.getHours() > 10 ? date.getHours() : "0" + date.getHours(),
        mm:
          date.getMinutes() > 10 ? date.getMinutes() : "0" + date.getMinutes(),
        ss:
          date.getSeconds() > 10 ? date.getSeconds() : "0" + date.getSeconds(),
      };
      for (const key in config) {
        format = format.replace(key, config[key]);
      }
      return format;
    },
  },
};
</script>

<style lang="less">
#simulation-process {
  width: 100%;
  height: 100vh;
  // background-color: rgba(105, 105, 105, 0.8);
  // color: rgba(220, 220, 220, 1);

  display: flex;
  flex-direction: column;

  .body {
    width: 100%;
    flex: 1;
    position: relative;
    overflow: hidden;

    display: flex;

    .left {
      width: 280px;
    }

    .right {
      flex: 1;
      position: relative;

      display: flex;
      flex-direction: column;

      .editor-panel-wrapper {
        width: 100%;
        flex: 1;
      }
    }

    .minimap-wrapper {
      width: 240px;
      height: 160px;
      position: absolute;
      right: 10px;
      bottom: 10px;
      box-sizing: border-box;
      border: 1px solid rgba(0, 0, 0, 0.08);
      border-radius: 4px;
      overflow: hidden;

      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
  }
}

// 处理 滚动条 样式
.x6-graph-scroller {
  &::-webkit-scrollbar {
    width: 6px;
    height: 6px;
  }
  &::-webkit-scrollbar-thumb {
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.2);
  }
  &::-webkit-scrollbar-track {
    background-color: #f1f1f1;
  }
}

.html-port-in {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  // visibility: visible;
  // color: whitesmoke;
  // color: #606266;
  color: black;
  line-height: 20px;

  display: flex;
  justify-content: flex-start;
  align-items: center;
  &:hover {
    background-color: rgba(255, 255, 255, 0.5);
  }

  .html-port-name {
    font-size: 10px;
    font-weight: bold;
  }
}

.html-port-out {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  // visibility: visible;
  // color: whitesmoke;
  // color: #606266;
  color: black;
  line-height: 20px;

  display: flex;
  justify-content: flex-end;
  align-items: center;
  &:hover {
    background-color: rgba(255, 255, 255, 0.5);
  }

  .html-port-name {
    font-size: 10px;
    font-weight: bold;
  }
}

.x6-node [magnet="true"] {
  transition: none;
}
</style>
